# Port of hal.tcl (Eggdrop "learn" script)
# Orginially by Hal9000 @ irc.ptnet.org

import string
import time
import fileinput
from collections import defaultdict
import operator

import config

irc = None
hal_doc = "learn.dat"
hal_log = "learn.log"
channels = ["#liek"]
flood_list = {}

def init(bot):
    global irc, hal_doc, channels, hal_log

    bot.cmd.events['PRIVMSG'].subscribe(privmsg)
    irc = bot
    
    try:
        hal_doc = config.hal_dat
    except:
        pass
    try:
        channels = config.hal_channels
    except:
        pass
    try:
        hal_log = config.hal_log
    except:
        pass

    print "hal.py by Matt@#liek loaded."

def privmsg(sender, args):
    global irc, hal_doc, channels, flood_list

    if irc.irclower(args[2]) not in channels:
        # ignore -> we don't work here
        return

    argv = args[0].split()
    
    if len(argv) == 0:
        return

    if len(argv[0]) == 0 or argv[0].isspace():
        return

    if argv[0] == '??':
        if len(argv) < 2:
            irc.notice(sender.nick, "Try ?? word || ?? word > target")
            return
        
        term = argv[1].lower()
        target = irc.irclower(args[2])
        changed_target = 0

        if len(argv) >= 3:
            for param in range(2, len(argv)-1):
                if argv[param].startswith('>'):
                    if param == len(argv)-1:
                        irc.notice(sender.nick, "Try ?? word || ?? word > target")
                        return
                    target = irc.irclower(argv[param+1])
                    changed_target = 1
                    break
            if changed_target:
                term = ' '.join(argv[1:param]).lower()
            else:
                term = ' '.join(argv[1:]).lower()
        term_escaped = term_escape(term)

        if flood_list.has_key((term, target)):
            if int(time.time()) - flood_list[(term, target)] > 60:
                del flood_list[(term, target)]
            else:
                irc.privmsg(args[2], "\002Hey %s, I've already told you about \"%s\"... no need to repeat (I think)\002" % (sender.nick, term))
                return
        
        flood_list[(term, target)] = int(time.time())

        try:
            fh = open(hal_doc)
        except IOError:
            irc.privmsg(args[2], 'Error opening definition file!')
            return

        matched_lines = []

        for line in fh:
            l = line.rstrip()

            split_line = l.split(None, 2)

            if len(split_line) < 3:
                continue
            
            if split_line[1] == term_escaped:
                matched_lines.append(split_line[:])
        fh.close()

        # XXX: Here we should be checking who we're sending to

        if len(matched_lines) < 1:
            irc.privmsg(target, get_output(term, 'x', 'No definition found for word.'))
        elif len(matched_lines) == 1:
            irc.privmsg(target, get_output(term, None, matched_lines[0][2]))
        else:
            count = 0
            for i in matched_lines:
                count += 1
                irc.privmsg(target, get_output(term, count, i[2]))

    elif argv[0] == '**':
        if len(argv) < 2:
            irc.privmsg(args[2], "Not enough arguments.")
            return

        _, search = args[0].split(' ', 1)
        search = search.lower()

        start_clock = time.clock()

        try:
            fh = open(hal_doc)
        except IOError:
            irc.privmsg(args[2], 'Error opening definition file!')
            return
        
        matched_terms = []

        for line in fh:
            l = line.rstrip()

            split_line = l.split(None, 1)

            if len(split_line) < 2:
                continue
            
            term = split_line[1].split(None, 1)[0]
            term = term.lower()
            split_line[1] = split_line[1].lower()

            if term in matched_terms:
                continue
            
            if search in split_line[1]:
                if len(matched_terms) >= 10:
                    time_elapsed = time.clock() - start_clock
                    irc.privmsg(args[2], "\002Too many matches.\002 Try a more complex search. (\002\037%s\037s\002)" % round(time_elapsed, 3))
                    fh.close()
                    return

                matched_terms.append(term)
        fh.close()

        if len(matched_terms) > 0:
            matched_terms.sort()
            
            idx = 0
            while idx < len(matched_terms):
                matched_terms[idx] = ("\037%s\037" % matched_terms[idx])
                idx = idx + 1
            
            time_elapsed = time.clock() - start_clock

            irc.privmsg(args[2], ("\002Found \037%d\037 matches.\002 Sorted: %s. (\002\037%s\037s\002)" % (len(matched_terms), term_unescape(string.join(matched_terms, ', ')), round(time_elapsed, 3))))
        else:
            time_elapsed = time.clock() - start_clock
            irc.privmsg(args[2], ("Sorry, no matches for %s in the definition database. (\002\037%s\037s\002)" % (search, round(time_elapsed, 3))))

    elif argv[0] == "!learn":
        # XXX: Needs an auth system, but for now we'll go for chanops
        if not irc.chmgr.get(args[2]).isop(sender.nick):
            return

        # !learn command term [idx] [def]

        if len(argv) < 2:
            irc.notice(sender.nick, "Syntax: !learn (add, rem, ins, rep) term [index] [definition]")
            return
        
        if argv[1] == 'add':
            if len(argv) < 4:
                irc.notice(sender.nick, "Try !learn add word definition")
                return

            _, _, term, definition = args[0].split(None, 3)
            if term[0] == '"':
                if len(term) < 2:
                    irc.notice(sender.nick, "Invalid syntax")
                    return
                if term[-1] == '"':
                    if len(term) < 3:
                        irc.notice(sender.nick, "Invalid syntax")
                        return
                    term = term[1:-1]
                else:
                    # Multiword term
                    term_end, _, definition = definition.partition('" ')
                    if not definition or definition.isspace():
                        irc.notice(sender.nick, "Invalid syntax")
                        return
                    term = term[1:] + ' ' + term_end
                    if term.isspace():
                        irc.notice(sender.nick, "Invalid syntax")
                        return
                    term = ' '.join(term.split())
            term = term.replace('"', '')
            if term == "" or term.isspace():
                irc.notice(sender.nick, "Invalid syntax")
                return
            if not definition or definition.isspace():
                irc.notice(sender.nick, "Invalid syntax")
                return
            term = term.lower()
            term_escaped = term_escape(term)
            inserted = False
            count = 1
            try:
                for line in fileinput.input(hal_doc, inplace=1):
                    if not inserted:
                        _, line_term, _ = line.split(None, 2)
                        line_term = line_term.lower()
                        line_term = term_unescape(line_term)
                        if line_term == term:
                            count += 1
                        if line_term > term:
                            print "%s %s %s" % (sender.nick, term_escaped, definition)
                            inserted = True
                            hal_logger('add', sender, term, count, definition)
                    print line,
            except IOError:
                irc.notice(sender.nick, "Unable to open dictionary file.")
                hal_logger('error', sender, "Error writing to dictionary file. (add)")
                hal_savelog()

            if not inserted:
                try:
                    fh = open(hal_doc, 'a')
                    fh.write("%s %s %s\n" % (sender.nick, term_escaped, definition))
                    fh.close()
                    hal_logger('add', sender, term, count, definition)
                except IOError:
                    irc.notice(sender.nick, "Error writing to dictionary file.")
                    hal_logger('error', sender, "Error writing to dictionary file. (add)")
                    hal_savelog()
                    return

            irc.notice(sender.nick, "Definition added.")

        elif argv[1] == 'rem':
            if len(argv) == 3:
                remove = 0
                term = argv[2].strip('"')
            else:
                term = argv[2]
                if term[0] == '"':
                    if term[-1] == '"':
                        if len(term) <= 2:
                            irc.notice(sender.nick, "Invalid syntax")
                            return
                        term = term[1:-1]
                        rem_idx = argv[3]
                    else:
                        # Multiword term
                        if len(argv) < 4:
                            irc.notice(sender.nick, "Invalid syntax")
                            return
                        _, _, _, req = args[0].split(None, 3)
                        term_end, check_term, rem_idx = req.partition('" ')
                        if not check_term:
                            irc.notice(sender.nick, "Invalid syntax")
                            return
                        term = term[1:] + ' ' + term_end
                        term = ' '.join(term.split())
                        rem_idx = rem_idx.strip()
                else:
                    rem_idx = argv[3]
                    term = argv[2]
                    
                if not rem_idx.isdigit():
                    remove = 0
                else:
                    remove = int(rem_idx)

            term = term.replace('"', '')
            if term == "" or term.isspace():
                irc.notice(sender.nick, "Invalid syntax")
                return
            term = term.lower()
            term_escaped = term_escape(term)

            count = 0
            removed = False
            found_any = False
            try:
                for line in fileinput.input(hal_doc, inplace=1):
                    if not removed:
                        _, line_term, _ = line.split(None, 2)
                        line_term = line_term.lower()
                        if line_term == term_escaped:
                            count = count + 1
                            if count == remove:
                                removed = True
                                hal_logger('rem', sender, term, count, line)
                                continue
                            elif remove == 0:
                                # Remove all
                                found_any = True
                                hal_logger('rem', sender, term, count, line)
                                continue
                    print line,
            except IOError:
                irc.notice(sender.nick, "Error writing to dictionary file.")
                hal_logger('error', sender, "Error writing to definition file. (rem)")
                hal_savelog()
                return
            
            if removed or found_any:
                irc.notice(sender.nick, "Definition removed.")
            else:
                irc.notice(sender.nick, "Couldn't find that word in my dictionary.")

        elif argv[1] == 'ins' or argv[1] == 'rep':
            # !learn ins/rep word idx def
            if len(argv) < 5:
                irc.notice(sender.nick, "Try !learn %s word loc definition", argv[1])
                return
            else:
                term = argv[2]
                if term[0] == '"':
                    # Kill me, please
                    if len(term) < 2:
                        irc.notice(sender.nick, "Invalid syntax")
                        return
                    if term[-1] == '"':
                        if len(term) < 3:
                            irc.notice(sender.nick, "Invalid syntax")
                            return
                        term = term[1:-1]
                        rem_idx = argv[3]
                        _, _, _, _, definition = args[0].split(None, 4)
                    else:
                        # Multiword term
                        if len(argv) < 6:
                            irc.notice(sender.nick, "Invalid syntax")
                            return
                        _, _, _, req = args[0].split(None, 3)
                        term_end, check_term, idx_and_def = req.partition('" ')
                        if not check_term:
                            irc.notice(sender.nick, "Invalid syntax")
                            return
                        term = term[1:] + ' ' + term_end
                        term = ' '.join(term.split())
                        try:
                            rem_idx, definition = idx_and_def.split(None, 1)
                        except ValueError:
                            irc.notice(sender.nick, "Invalid syntax")
                            return
                else:
                    rem_idx = argv[3]
                    _, _, _, _, definition = args[0].split(None, 4)

                if not rem_idx.isdigit():
                    irc.notice(sender.nick, "Location must be an integer")
                    return
                location = int(rem_idx)
                if location < 1:
                    irc.notice(sender.nick, "Location starts at 1")
                    return
            
            term = term.replace('"', '')
            term = term.replace('>', '')
            if term == "" or term.isspace():
                irc.notice(sender.nick, "Invalid syntax")
                return
            if not definition or definition.isspace():
                irc.notice(sender.nick, "Invalid syntax")
                return
            term = term.lower()
            term_escaped = term_escape(term)
            
            if argv[1] == 'ins':
                count = 0
                inserted = False

                # XXX: We assume the file is sorted properly. If it isn't: your fault when things go wrong :)
                try:
                    for line in fileinput.input(hal_doc, inplace=1):
                        if not inserted:
                            _, line_term, _ = line.split(None, 2)
                            line_term = line_term.lower()
                            line_term_unescaped = term_unescape(line_term)
                            if line_term == term_escaped:
                                count = count + 1
                                if count >= location:
                                    print "%s %s %s" % (sender.nick, term_escaped, definition)
                                    inserted = True
                                    hal_logger('ins', sender, term, location, definition)
                            elif line_term_unescaped > term:
                                print "%s %s %s" % (sender.nick, term_escaped, definition)
                                inserted = True
                                hal_logger('ins', sender, term, location, definition)
                        print line,
                except IOError:
                    irc.notice(sender.nick, "Error opening dictionary file.")
                    hal_logger('error', sender, "Error opening dictionary file. (ins)")
                    hal_savelog()
                
                if not inserted:
                    # Ouch, EOF
                    try:
                        fh = open(hal_doc, 'a')
                        fh.write("%s %s %s\n" % (sender.nick, term, definition))
                        fh.close()
                        hal_logger('ins', sender, location, term, definition)
                    except IOError:
                        irc.notice(sender.nick, "Error writing to dictionary file.")
                        hal_logger('error', "Error writing to dictionary file. (ins)")
                        hal_savelog()
                        return
                
                irc.notice(sender.nick, "Definition inserted")

            elif argv[1] == 'rep': 
                replaced = False
                count = 0

                try:
                    for line in fileinput.input(hal_doc, inplace=1):
                        if not replaced:
                            _, line_term, _ = line.split(None, 2)
                            line_term = line_term.lower()
                            if line_term == term_escaped:
                                count = count + 1
                                if count == location:
                                    print "%s %s %s" % (sender.nick, term_escaped, definition)
                                    replaced = True
                                    hal_logger('rep', sender, line, term, count, definition)
                                else:
                                    print line,
                            else:
                                print line,
                        else:
                            print line,
                except IOError:
                    irc.notice(sender.nick, "Error opening dictionary file.")
                    hal_logger('error', sender, "Error opening dictionary file. (rep)")
                    hal_savelog()
                    return
                
                if replaced:
                    irc.notice(sender.nick, 'Definition replaced.')
                else:
                    irc.notice(sender.nick, "I couldn't find that definition in my dictionary.")
                    irc.notice(sender.nick, ' - '.join((term, rem_idx, definition)))

        elif argv[1] == 'stats':
            try:
                fh = open(hal_doc)
            except IOError:
                irc.privmsg(args[2], 'Error opening definition file!')
                return

            terms = defaultdict(int)
            authors = defaultdict(int)
            total = 0

            for line in fh:
                l = line.rstrip()
                split_line = l.split(None, 2)

                if len(split_line) < 3:
                    continue

                terms[split_line[1].lower()] += 1
                authors[split_line[0].lower()] += 1
                total += 1
            fh.close()

            most_defs = max(terms.iteritems(), key=operator.itemgetter(1))[0]
            most_written = max(authors.iteritems(), key=operator.itemgetter(1))[0]

            irc.privmsg(args[2], "\002Stats\002: Terms: \037%d\037 Definitions: \037%d\037 Most Definitions: \002%s\002 (\037%d\037) Most Authored: \002%s\002 (\037%d\037)" %
                                    (len(terms), total, term_unescape(most_defs), terms[most_defs], most_written, authors[most_written]))
        hal_savelog()

def hal_logger(action, sender, *args):
    if action:
        hal_logger.events.append( {'action': action, 'sender': sender, 'time': time.localtime(), 'args': args} )
    else:
        if len(hal_logger.events) < 1:
            return
        
        log_fh = None
        try:
            log_fh = open(hal_log, 'a')
        except:
            return
        
        sender = ""
        for e in hal_logger.events:
            if not sender:
                log_fh.write("%s : %s!%s@%s\n" % (time.strftime("%a %b %d %H:%M:%S %Y", e['time']), e['sender'].nick, e['sender'].user, e['sender'].host))
                sender = e['sender'].nick
            elif sender != e['sender'].nick:
                log_fh.write("-\n")
                log_fh.write("%s : %s!%s@%s\n" % (time.strftime("%a %b %d %H:%M:%S %Y", e['time']), e['sender'].nick, e['sender'].user, e['sender'].host))
            if e['action'] == 'add':
                log_fh.write('Added: "%s"[%d] %s\n' % (e['args'][0], e['args'][1], e['args'][2]))
            elif e['action'] == 'rem':
                log_fh.write('Removed: "%s"[%d] %s' % (e['args'][0], e['args'][1], e['args'][2]))
            elif e['action'] == 'ins':
                log_fh.write('Inserted: "%s"[%d] %s\n' % (e['args'][0], e['args'][1], e['args'][2]))
            elif e['action'] == 'rep':
                log_fh.write('Replaced: "%s"[%d] %sWith: %s\n' % (e['args'][1], e['args'][2], e['args'][0], e['args'][3]))
            elif e['action'] == 'error':
                log_fh.write('Error: %s\n' % (e['args'][0]))
        log_fh.write("-\n")
        log_fh.close()
        hal_logger.events = []
        
hal_logger.events = []

def hal_savelog():
    hal_logger(None, None)
        
def get_output(word, index, definition):
    if index:
        return "\002\037%s[\037\002%s\002\037]\037\002: %s" % (word, index, definition)
    else:
        return "\002\037%s\037\002: %s" % (word, definition)

def clear_flood(term, target):
    global flood_list

    flood_list.remove((term, target))

def term_unescape(term):
    unescaped = term.replace('%20', ' ')
    unescaped = unescaped.replace('%25', '%')
    return unescaped

def term_escape(term):
    escaped = term.replace('%', '%25')
    escaped = escaped.replace(' ', '%20')
    return escaped
